// Sanitizer

'use strict';

module.exports = function sanitizer_plugin(md, options) {
    var linkify = md.linkify,
        escapeHtml = md.utils.escapeHtml,
        // <a href="url" title="(optional)"></a>
        patternLinkOpen = '<a\\s([^<>]*href="[^"<>]*"[^<>]*)\\s?>',
        regexpLinkOpen = RegExp(patternLinkOpen, 'i'),
        // <img src="url" alt=""(optional) title=""(optional)>
        patternImage = '<img\\s([^<>]*src="[^"<>]*"[^<>]*)\\s?\\/?>',
        regexpImage = RegExp(patternImage, 'i'),
        regexpImageProtocols = /^(?:https?:)?\/\//i,
        regexpLinkProtocols = /^(?:https?:\/\/|ftp:\/\/|\/\/|mailto:|xmpp:)/i;

    options = options ? options : {};
    var removeUnknown =
        typeof options.removeUnknown !== 'undefined'
            ? options.removeUnknown
            : false;
    var removeUnbalanced =
        typeof options.removeUnbalanced !== 'undefined'
            ? options.removeUnbalanced
            : false;
    var imageClass =
        typeof options.imageClass !== 'undefined' ? options.imageClass : '';
    var extraTags =
        typeof options.extraTags !== 'undefined' ? options.extraTags : [];
    var restrictTags =
        typeof options.restrictTags !== 'undefined' ? options.restrictTags : [];
    var allowImages = options.allowImages !== false ? true : false;
    var runBalancer = false;
    var j;

    var allowedTags = [
        'a',
        'b',
        'blockquote',
        'code',
        'em',
        'h1',
        'h2',
        'h3',
        'h4',
        'h5',
        'h6',
        'li',
        'ol',
        'p',
        'pre',
        's',
        'sub',
        'sup',
        'strong',
        'ul',
        'details',
        'summary',
    ];
    if (extraTags.length) {
        allowedTags = allowedTags.concat(extraTags);
    }
    if (restrictTags.length) {
        for (var i = 0; i < restrictTags.length; ++i) {
            var c = restrictTags[0];
            var idx = allowedTags.indexOf(c);
            if (idx > -1) {
                allowedTags.splice(idx, 1);
            }
        }
    }

    var openTagCount = new Array(allowedTags.length);
    var removeTag = new Array(allowedTags.length);
    for (j = 0; j < allowedTags.length; j++) {
        openTagCount[j] = 0;
    }
    for (j = 0; j < allowedTags.length; j++) {
        removeTag[j] = false;
    }

    function getUrl(link) {
        var match = linkify.match(link);
        if (
            match &&
            match.length === 1 &&
            match[0].index === 0 &&
            match[0].lastIndex === link.length
        ) {
            return match[0].url;
        }
        return null;
    }

  /////////////////////////////////////////////////////////////////////////////////////////////////
  //          REPLACE UNKNOWN TAGS
  /////////////////////////////////////////////////////////////////////////////////////////////////

    function replaceUnknownTags(str) {
        /*
         * it starts with '<' and maybe ends with '>',
         * maybe has a '<' on the right
         * it doesnt have '<' or '>' in between
         * -> it's a tag!
         */
        str = str.replace(/<[^<>]*>?/gi, function (tag) {
            var match, attrs, url, alt, title, tagnameIndex;

            // '<->', '<- ' and '<3 ' look nice, they are harmless
            if (/(^<->|^<-\s|^<3\s)/.test(tag)) {
                return tag;
            }

            // images
            match = tag.match(regexpImage);
            if (match && allowImages) {
                attrs = match[1];
                url = getUrl(attrs.match(/src="([^"<>]*)"/i)[1]);
                alt = attrs.match(/alt="([^"<>]*)"/i);
                alt = alt && typeof alt[1] !== 'undefined' ? alt[1] : '';
                title = attrs.match(/title="([^"<>]*)"/i);
                title = title && typeof title[1] !== 'undefined' ? title[1] : '';

        // only http and https are allowed for images
        if (url && regexpImageProtocols.test(url)) {
          if (imageClass !== '') {
              return (
                  '<img src="' +
                  url +
                  '" alt="' +
                  alt +
                  '" title="' +
                  title +
                  '" class="' +
                  imageClass +
                  '">'
              );
          }
            return (
                '<img src="' + url + '" alt="' + alt + '" title="' + title + '">'
            );
        }
      }

      // links
      tagnameIndex = allowedTags.indexOf('a');
      match = tag.match(regexpLinkOpen);
      if (match) {
        attrs = match[1];
        url = getUrl(attrs.match(/href="([^"<>]*)"/i)[1]);
          title = attrs.match(/title="([^"<>]*)"/i);
          title = title && typeof title[1] !== 'undefined' ? title[1] : '';
        // only http, https, ftp, mailto and xmpp are allowed for links
        if (url && regexpLinkProtocols.test(url)) {
          runBalancer = true;
          openTagCount[tagnameIndex] += 1;
          return '<a href="' + url + '" title="' + title + '" target="_blank">';
        }
      }
      match = /<\/a>/i.test(tag);
      if (match) {
        runBalancer = true;
        openTagCount[tagnameIndex] -= 1;
        if (openTagCount[tagnameIndex] < 0) {
          removeTag[tagnameIndex] = true;
        }
          return '</a>';
      }

            // standalone tags
            match = tag.match(/<(br|hr)\s?\/?>/i);
            if (match) {
                return '<' + match[1].toLowerCase() + '>';
            }

            // whitelisted tags
            match = tag.match(
                /<(\/?)(b|blockquote|code|em|h[1-6]|li|ol(?: start="\d+")?|p|pre|s|sub|sup|strong|ul|details|summary)>/i,
            );
            if (match && !/<\/ol start="\d+"/i.test(tag)) {
                runBalancer = true;
                tagnameIndex = allowedTags.indexOf(
                    match[2].toLowerCase().split(' ')[0],
                );
                if (match[1] === '/') {
                    openTagCount[tagnameIndex] -= 1;
                } else {
                    openTagCount[tagnameIndex] += 1;
                }
                if (openTagCount[tagnameIndex] < 0) {
                    removeTag[tagnameIndex] = true;
                }
                return '<' + match[1] + match[2].toLowerCase() + '>';
            }

      // other tags we don't recognize
      if (removeUnknown === true) {
        return '';
      }
      return escapeHtml(tag);
    });

    return str;
  }

    function sanitizeInlineAndBlock(state) {
        var i, blkIdx, inlineTokens;
        // reset counts
        for (j = 0; j < allowedTags.length; j++) {
            openTagCount[j] = 0;
        }
        for (j = 0; j < allowedTags.length; j++) {
            removeTag[j] = false;
        }
        runBalancer = false;

        for (blkIdx = 0; blkIdx < state.tokens.length; blkIdx++) {
            if (state.tokens[blkIdx].type === 'html_block') {
                state.tokens[blkIdx].content = replaceUnknownTags(
                    state.tokens[blkIdx].content,
                );
            }
            if (state.tokens[blkIdx].type !== 'inline') {
                continue;
      }

      inlineTokens = state.tokens[blkIdx].children;
      for (i = 0; i < inlineTokens.length; i++) {
        if (inlineTokens[i].type === 'html_inline') {
            inlineTokens[i].content = replaceUnknownTags(inlineTokens[i].content);
        }
      }
        }
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////
    //          REPLACE UNBALANCED TAGS
    /////////////////////////////////////////////////////////////////////////////////////////////////

    function balance(state) {
        if (runBalancer === false) {
            return;
        }
        var blkIdx, inlineTokens;

        function replaceUnbalancedTag(str, tagname) {
            var openingRegexp, closingRegexp;
            if (tagname === 'a') {
                openingRegexp = RegExp(
                    '<a href="[^"<>]*" title="[^"<>]*" target="_blank">',
                    'g',
                );
            } else if (tagname === 'ol') {
                openingRegexp = /<ol(?: start="\d+")?>/g;
            } else {
                openingRegexp = RegExp('<' + tagname + '>', 'g');
      }
      closingRegexp = RegExp('</' + tagname + '>', 'g');
      if (removeUnbalanced === true) {
        str = str.replace(openingRegexp, '');
        str = str.replace(closingRegexp, '');
      } else {
          str = str.replace(openingRegexp, function (m) {
              return escapeHtml(m);
          });
          str = str.replace(closingRegexp, function (m) {
              return escapeHtml(m);
          });
      }
      return str;
    }

        function replaceAllUnbalancedTags(str) {
            var i;
            for (i = 0; i < allowedTags.length; i++) {
                if (removeTag[i] === true) {
                    str = replaceUnbalancedTag(str, allowedTags[i]);
                }
            }
            return str;
        }

    for (j = 0; j < allowedTags.length; j++) {
      if (openTagCount[j] !== 0) {
        removeTag[j] = true;
      }
    }

    // replace unbalanced tags
    for (blkIdx = 0; blkIdx < state.tokens.length; blkIdx++) {
      if (state.tokens[blkIdx].type === 'html_block') {
          state.tokens[blkIdx].content = replaceAllUnbalancedTags(
              state.tokens[blkIdx].content,
          );
          continue;
      }
      if (state.tokens[blkIdx].type !== 'inline') {
        continue;
      }
      inlineTokens = state.tokens[blkIdx].children;
      for (j = 0; j < inlineTokens.length; j++) {
        if (inlineTokens[j].type === 'html_inline') {
            inlineTokens[j].content = replaceAllUnbalancedTags(
                inlineTokens[j].content,
            );
        }
      }
    }
  }

  md.core.ruler.after('linkify', 'sanitize_inline', sanitizeInlineAndBlock);
  md.core.ruler.after('sanitize_inline', 'sanitize_balance', balance);
};
